#region copyright
/*
* Copyright (c) 2009, Dion Kurczek
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the <organization> nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY DION KURCZEK ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL DION KURCZEK BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using SCG.General;
using System.IO;
using System.Reflection;

namespace SCG.Prism.Server
{
    //PrismServer component encapsulates a PrismServer and manages client connections
    public class PrismServer
    {
        //locks
        public object GuestLock = new object();
        public object RoomLock = new object();

        //Constructors
        public PrismServer()
        {
        }
        public PrismServer(PrismServerStorage storage)
        {
            Storage = storage;
        }
        private void InitializeBasePath()
        {
             BasePath = Assembly.GetExecutingAssembly().Location;
             BasePath = System.IO.Path.GetDirectoryName(BasePath);
        }

        //Event Handlers
        public event EventHandler<PrismGuestEventArgs> GuestConnected;
        public event EventHandler<PrismGuestEventArgs> GuestDisconnected;
        public event EventHandler<EventArgs> Pinging;
        public event EventHandler<PrismGuestEventArgs> GuestDisconnectedSameIP;
        public event EventHandler<PrismGuestEventArgs> GuestLoggedIn;
        public event EventHandler<PrismRoomEventArgs> RoomAdded;
        public event EventHandler<PrismRoomEventArgs> RoomRemoved;
        public event EventHandler<PrismGuestMsgEventArgs> GuestChat;
        public event EventHandler<ServerCustomCommandEventArgs> CustomCommandReceived;
        public event EventHandler<ExceptionEventArgs> ExceptionOcurred;

        //The base path for PrismServer to save files
        public string BasePath { get; set; }

        //Storage component - implements user management behavior
        public PrismServerStorage Storage
        {
            get
            {
                return _storage;
            }
            set
            {
                //Do not allow implementation to be changed while Active
                if (Active)
                    throw new PrismServerException("Cannot set Storage property while PrismServer is Active");          
                _storage = value;
                if (_storage != null)
                    _storage.Server = this;                  
            }
        }

        //Custom command processor
        private List<CustomCommandProcessor> _commandModules = new List<CustomCommandProcessor>();
        public List<CustomCommandProcessor> CommandModules
        {
            get
            {
                return _commandModules;
            }
        }
        public void InstallCommandModule(CustomCommandProcessor cc)
        {
            cc.Initialize(this);
            _commandModules.Add(cc);
        }

        //Access to PrismGuests        
        public List<PrismGuest> Guests
        {
            get
            {
                return _guestList;
            }
        }

        //Find a guest by name
        public PrismGuest FindGuest(string userName)
        {
            lock (GuestLock)
            {
                foreach (PrismGuest guest in Guests)
                    if (guest.User != null)
                        if (guest.User.UserName == userName)
                            return guest;
            }
            return null;
        }

        //Return a User object even if that user isn't currently logged in
        public PrismUser FindUser(string userName)
        {
            PrismUser found = null;
            lock (GuestLock)
            {
                foreach(PrismGuest guest in Guests)
                    if (guest.User != null)
                        if (guest.User.UserName == userName)
                        {
                            found = guest.User;
                            break;
                        }
            }

            if (found == null)
                found = _storage.FindUser(userName);

            return found;
        }

        //Access to Rooms
        public List<PrismRoom> Rooms
        {
            get
            {
                return _roomList;
            }
        }

        //Find a room by name
        public PrismRoom FindRoom(string subjectName, string roomName)
        {
            lock(RoomLock)
            {
                foreach (PrismRoom room in Rooms)
                    if (room.SubjectName == subjectName && room.RoomName == roomName)
                        return room;
            }
            return null;
        }

        //Active property - set to true to start server listening
        public bool Active
        {           
            get
            {
                return _active;
            }
            set
            {             
                if (_active != value)
                {
                    //Activate server
                    if (value)
                    {
                        //Do not allow activation if Implementation has not been assigned
                        if (Storage == null)
                            throw new PrismServerException("Cannot activate PrismServer without assigning Storage property");

                        //Create listener socket
                        _listener = new TcpListener(IPAddress.Any, Port);                     

                        //Activate socket
                        _listener.Start();
                        _activated = DateTime.Now;

                        //Create a thread to listen for pending connections
                        _listenThread = new Thread(ExecuteListenerThread);
                        _listenThread.IsBackground = true;
                        _listenThread.Start();

                        //Finally, set Active property after connection succesfully established
                        _active = true;

                        //Start pinging
                        _pingThread = new Thread(new ThreadStart(PingThread));
                        _pingThread.IsBackground = true;
                        _pingThread.Start();
                    }

                    //De-activate Server
                    else
                    {
                        _pingThread.Abort();
                        _pingThread = null;

                        //Set Active property false first, so listening thread processing method has correct
                        //value during next pass
                        _active = false;
                        _listener.Stop();

                        //Close all connected sockets
                        for (int i = Guests.Count - 1; i >= 0; i--)
                            Guests[i].Stream.Close();
                    
                        //Clear all guest objects
                        Guests.Clear();
                    }                 
                }
            }
        }

        //Determine when the server was activated
        public DateTime WhenActivated
        {
            get
            {
                return _activated;
            }
        }

        //Port number that the server will listen on
        public int Port
        {
            get
            {
                return _port;
            }
            set
            {
                if (!Active)
                    _port = value;
            }
        }

        //PingInterval property determines the number of seconds to wait before pinging clients
        public int PingInterval
        {
            get
            {
                return _pingInterval;
            }
            set
            {
                _pingInterval = value;
                _pingCycles = _pingInterval * 100;
            }
        }

        //The default Lobby name
        public string LobbyName
        {
            get
            {
                return _lobbyName;
            }
            set
            {
                _lobbyName = value;
            }
        }

        //Prohibit multiple connections from same IP?
        public bool ProhibitSameIP
        {
            get
            {
                return _prohibitSameIP;
            }
            set
            {
                _prohibitSameIP = value;
            }
        }

        //Prohibit multiple logins from same User?
        public bool ProhibitSameUserName
        {
            get
            {
                return _prohibitSameUserName;
            }
            set
            {
                _prohibitSameUserName = value;
            }
        }

        //Access number of bytes read/written
        public long BytesRead
        {
            get
            {
                return _bytesRead;
            }
        }
        public long BytesWritten
        {
            get
            {
                return _bytesWritten;
            }        
        }

        //Access other server statistics
        public int MaxGuestCount
        {
            get
            {
                return _maxGuestCount;
            }
        }
        public int MaxRoomCount
        {
            get
            {
                return _maxRoomCount;
            }
        }
        public int TotalConnections
        {
            get
            {
                return _connections;
            }
        }
        public int TotalRooms
        {
            get
            {
                return _rooms;
            }
        }

        //Send to all guests who are in the current subject
        public void SendToSubject(string subjectName, params object[] tokens)
        {       
            for (int n = Guests.Count - 1; n >= 0; n--)
            {
                try
                {
                    PrismGuest guest = Guests[n];
                    if (guest.SubjectName == subjectName)
                        guest.WriteTokens(tokens);
                }
                catch(Exception ex)
                {
                    if (ExceptionOcurred != null)
                        ExceptionOcurred(this, new ExceptionEventArgs(ex, "SendToSubject"));
                }
            }            
        }

        //Remove a PrismGuest from the server
        public void RemoveGuest(PrismGuest Guest)
        {
            //Only process removal logic one time
            if (!Guest.WasRemoved)
            {
                Guest.WasRemoved = true;
                //Alert other members in the room that he is leaving
                try
                {
                    Guest.Room = null;
                }
                catch(Exception ex)
                {
                    if (ExceptionOcurred != null)
                        ExceptionOcurred(this, new ExceptionEventArgs(ex, "RemoveGuest"));
                }
                //Try to close socket
                try
                {
                    Guest.Stream.Close();
                }
                catch(Exception ex)
                {
                    if (ExceptionOcurred != null)
                        ExceptionOcurred(this, new ExceptionEventArgs(ex, "RemoveGuest:2"));
                }

                //Remove from master list
                lock (GuestLock)
                {
                    Guests.Remove(Guest);
                }

                //Alert server app via event
                if (GuestDisconnected != null)
                    GuestDisconnected(this, new PrismGuestEventArgs(Guest));
            }
        }

        //Add a new room
        public void AddRoom(PrismRoom room)
        {
            lock (RoomLock)
            {
                Rooms.Add(room);

                //See if we have exceeded maximum room count                       
                if (Rooms.Count > _maxRoomCount)
                    _maxRoomCount = Rooms.Count;
                _rooms++;
            }

            //Notify clients that the room was added - avoid if this is the lobby
            if (room.RoomName != LobbyName)
                SendToSubject(room.SubjectName, "ROOMADDED", room.RoomName);

            //Trigger alert to notify client app
            if (RoomAdded != null)
                RoomAdded(this, new PrismRoomEventArgs(room));
        }

        //Remove a room that has no more guests
        public void RemoveRoom(PrismRoom room)
        {
            if (room.RoomGuests.Count == 0)
            {
                lock (RoomLock)
                {
                    Rooms.Remove(room);
                }

                //alert other guests in the same subject that the room has been removed
                SendToSubject(room.SubjectName, "ROOMREMOVED", room.RoomName);

                //Trigger alert to notify client app
                if (RoomRemoved != null)
                    RoomRemoved(this, new PrismRoomEventArgs(room));
            }
        }

        //Get an instance of server stats
        public PrismServerStats GetServerStats()
        {
            return new PrismServerStats(_activated.ToUniversalTime(), _bytesRead, _bytesWritten, Guests.Count,
                _connections, _maxGuestCount, _roomList.Count, _rooms, _maxRoomCount);
        }

        //Broadcast changes to a user to other guests in that user's room
        public void BroadcastUserInfoChanges(PrismUser user)
        {
            lock (RoomLock)
            {
                foreach (PrismRoom room in Rooms)
                {
                    bool inRoom = false;
                    lock (room.RoomGuestLock)
                    {
                        foreach (PrismGuest g in room.RoomGuests)
                            if (g.User.UserName == user.UserName)
                            {
                                //Since the parameter might be from a new instance, reload the new user info
                                Storage.StoreUserInfo(user);
                                Storage.PopulateUserInfo(g.User);
                                inRoom = true;
                            }
                    }
                    if (inRoom)
                        room.SendToGuests("USERINFOCHANGE", user.ToString());
                }
            }
        }

        //Private fields
        private int _port;
        private bool _active;
        private TcpListener _listener;
        private Thread _listenThread;
        private List<PrismGuest> _guestList = new List<PrismGuest>();
        private List<PrismRoom> _roomList = new List<PrismRoom>();
        private int _pingInterval = 30;
        private int _pingCycles = 3000;
        private PrismServerStorage _storage = null;
        private string _lobbyName = "Lobby";
        private DateTime _activated;
        private bool _prohibitSameIP = true;
        private bool _prohibitSameUserName = false;
        private long _bytesRead = 0;
        private long _bytesWritten = 0;
        private Object _readLock = new Object();
        private Object _writeLock = new Object();
        private int _maxGuestCount;
        private int _maxRoomCount;
        private int _connections = 0;
        private int _rooms = 0;
        private Thread _pingThread = null;

        //Thread execution method - listen on socket for pending connections
        private void ExecuteListenerThread()
        {          
            while (Active)
            {
                //Is there a client waiting for a connection?
                while (_listener.Pending())
                {
                    //Grab the socket from the client
                    TcpClient clientSocket;
                    clientSocket = _listener.AcceptTcpClient();

                    //Create a PrismGuest object for this connection and add it to the list                   
                    PrismNetworkStream pns = new PrismNetworkStream(clientSocket.Client);
                    PrismGuest newGuest = new PrismGuest(this, pns, clientSocket);
                 
                    //Prohibit multiple connections from this IP Address?
                    if (ProhibitSameIP)
                    {
                        lock (GuestLock)
                        {
                            foreach(PrismGuest guest in Guests)
                                if (guest.IPAddress.ToString() == newGuest.IPAddress.ToString())
                                {
                                    //Need to disconnect this guest, notify client via event
                                    if (GuestDisconnectedSameIP != null)
                                        GuestDisconnectedSameIP(this, new PrismGuestEventArgs(newGuest));

                                    //Disconnect them
                                    newGuest.WriteTokens("ERROR", "Multiple connections from same IP Address prohibited");
                                    Thread.Sleep(5000);
                                    pns.Close();                                 
                                    break;
                                }
                        }
                    }                   
                    //If connection was terminated, continue listening
                    if (newGuest.Stream.WasClosed)
                        continue;

                    //Add the guest to the local list
                    lock (GuestLock)
                    {
                        Guests.Add(newGuest);

                        //See if we have exceeded maximum guest count
                        _connections++;
                        if (Guests.Count > _maxGuestCount)
                            _maxGuestCount = Guests.Count;
                    }

                    //Trigger an event
                    if (GuestConnected != null)
                        GuestConnected(this, new PrismGuestEventArgs(newGuest));

                    //Create a thread to read data from the socket
                    Thread thrdClient = new Thread(ExecuteClientSocketThread);
                    thrdClient.IsBackground = true;
                    thrdClient.Start(newGuest);
                }
                Thread.Sleep(10);
            }           
        }

        //ping thread
        private void PingThread()
        {
            while (true)
            {
                Thread.Sleep(PingInterval * 1000);

                if (Active)
                {             
                    //Provide event hook whenever we ping
                    if (Pinging != null)
                        Pinging(this, new EventArgs());

                    //Ping each client - do not lock here because we need to remove guests
                    for (int i = Guests.Count - 1; i >= 0; i--)
                    {
                        try
                        {
                            PrismGuest g = Guests[i];

                            //add to bandwidth tally
                            lock (g.Stream)
                            {
                                _bytesRead += g.Stream.BytesRead;
                                g.Stream.BytesRead = 0;
                                _bytesWritten += g.Stream.BytesWritten;
                                g.Stream.BytesWritten = 0;
                            }

                            //If they have not responded to last ping, disconnect them!
                            if (g.WaitingForPing)
                                RemoveGuest(g);
                            else
                                g.Ping();
                        }
                        catch (Exception ex)
                        {
                            if (ExceptionOcurred != null)
                                ExceptionOcurred(this, new ExceptionEventArgs(ex, "Ping"));
                        }
                    }
                }
            }
        }

        //Thread execution method - pull data from a client socket - process it
        private void ExecuteClientSocketThread(object guest)
        {
            PrismGuest theGuest = (PrismGuest)guest;
            List<string> tokenList = new List<string>();
            string command, roomName, validity, userString;
            PrismRoom room;
                
            while (Active && !theGuest.Stream.WasClosed)
            {
                //Read messages from the PrismGuest using its PrismNetworkStream
                try
                {
                    command = theGuest.Stream.ReadTokens(tokenList);
                }
                catch(Exception ex)
                {
                    if (ExceptionOcurred != null)
                        ExceptionOcurred(this, new ExceptionEventArgs(ex, "ExecuteClientSocketThread"));
                    RemoveGuest(theGuest);
                    break;
                }

                //Remove client that disconnected
                if (command == "")
                {
                    RemoveGuest(theGuest);
                    break;
                }

                //In case we got shut down while reader loop was spinning
                if (!Active)
                    break;            

                //Process Known Prism Protocol commands
                switch (command)
                {
                    //Client returned a Ping 
                    case "PING":
                        theGuest.PingReturned();
                        break;

                    //Login as an existing user
                    case "LOGIN":
                        string UserName = tokenList[0];

                        //Enforce single user name sign in
                        if (CheckSingleUserName(UserName, theGuest))
                        {
                            string Password = tokenList[1];

                            //Does the user name exist in the account database?
                            if (Storage.UserExists(UserName))
                            {
                                //Is the supplied password valid?

                                if (Storage.IsPasswordValid(UserName, Password))
                                {
                                    theGuest.SubjectName = tokenList[2];

                                    //Populate user info for this user                                   
                                    theGuest.User.UserName = UserName;
                                    theGuest.User.Password = Password;
                                    Storage.PopulateUserInfo(theGuest.User);

                                    //Record this user's login
                                    theGuest.User.RecordLogin();
                                    Storage.StoreUserInfo(theGuest.User);

                                    //Create the lobby if it does not exist
                                    PrismRoom Lobby = GetLobby(theGuest.SubjectName);

                                    //Notify client Login was OK, send user info string
                                    theGuest.WriteTokens("LOGINOK", theGuest.User.ToString(), RoomListString(theGuest.SubjectName));

                                    //Add user to the Lobby                                   
                                    theGuest.Room = Lobby;

                                    //Notify client app via event
                                    if (GuestLoggedIn != null)
                                        GuestLoggedIn(this, new PrismGuestEventArgs(theGuest));
                                }
                                else
                                    theGuest.WriteTokens("LOGINERROR", "The specified Password is invalid");
                            }
                            else
                                theGuest.WriteTokens("LOGINERROR", "The specified User Name '" + UserName + "' does not exist");
                        }
                        break;

                    //Login as a new user
                    case "LOGINNEW":

                        //Populate user info from the User String obtained from client
                        userString = tokenList[0];
                        theGuest.User.FromString(userString);
                        theGuest.SubjectName = tokenList[1];

                        //Enforce single sign on of user name
                        if (CheckSingleUserName(theGuest.User.UserName, theGuest))
                        {
                            //Does the user already exist in the database?
                            if (!Storage.UserExists(theGuest.User.UserName))
                            {
                                //Are the supplied User Name and Password fields valid?
                                validity = "";
                                if (Storage.CheckUserName(theGuest.User.UserName, ref validity))
                                {
                                    if (Storage.CheckPassword(theGuest.User.Password, ref validity))
                                    {
                                        //Store the newly created user in the database
                                        theGuest.User.CreationDate = DateTime.Now;
                                        theGuest.User.RecordLogin();
                                        Storage.StoreUserInfo(theGuest.User);

                                        //Create the Lobby if it does not exist
                                        PrismRoom lobby = GetLobby(theGuest.SubjectName);
                                        //Notify client login was OK
                                        theGuest.WriteTokens("LOGINOK", theGuest.User.ToString(), RoomListString(theGuest.SubjectName));

                                        //Add them to the lobby                                        
                                        theGuest.Room = lobby;

                                        //Notify client app via event
                                        if (GuestLoggedIn != null)
                                            GuestLoggedIn(this, new PrismGuestEventArgs(theGuest));
                                    }
                                    else
                                        theGuest.WriteTokens("LOGINERROR", validity);
                                }
                                else
                                    theGuest.WriteTokens("LOGINERROR", validity);
                            }
                            else
                                theGuest.WriteTokens("LOGINERROR", "The specified User Name '" + theGuest.User.UserName + "' already exists");
                        }
                        break;

                    //Attempt to join an existing room
                    case "JOINROOM":
                        roomName = tokenList[0];
                        room = FindRoom(theGuest.SubjectName, roomName);

                        //Are they already in this room?
                        if (theGuest.Room != room)

                            //Does the room exist?
                            if (room != null)
                            {
                                //Yes, is it already locked?
                                if (!room.Locked)
                                {
                                    //No, OK to join the room
                                    theGuest.Room = room;
                                }
                                else
                                    theGuest.WriteTokens("ERROR", "The Room is locked");
                            }
                            else
                                theGuest.WriteTokens("ERROR", "There is no Room named '" + roomName + "'");
                        break;

                    //Attempt to create a new room
                    case "CREATEROOM":
                        roomName = tokenList[0];

                        //Does a room with this name already exist?
                        if (FindRoom(theGuest.SubjectName, roomName) == null)
                        {
                            //Are the room details valid?
                            int MaxUsers = Int32.Parse(tokenList[1]);
                            validity = "";
                            if (Storage.CheckRoom(roomName, MaxUsers, ref validity))
                            {
                                //Create the room
                                room = new PrismRoom(theGuest.SubjectName, roomName);
                                room.MaxGuests = MaxUsers;

                                //Add the room
                                AddRoom(room);

                                //The guest will now join the room he just created
                                theGuest.Room = room;
                            }
                            else
                                theGuest.WriteTokens("ERROR", validity);
                        }
                        else
                            theGuest.WriteTokens("ERROR", "A Room named '" + roomName + "' already exists");
                        break;

                    //Chat message - distribute to other guests in room
                    case "CHAT":
                        theGuest.Room.SendToGuests(theGuest, "CHAT", theGuest.User.UserName, tokenList[0]);

                        //Trigger chat evetn to client app
                        if (GuestChat != null)
                            GuestChat(this, new PrismGuestMsgEventArgs(theGuest, tokenList[0]));
                        break;

                    //Data message - distribute to other guests in room
                    case  "DATA":
                        theGuest.Room.SendToGuests(theGuest, "DATA", theGuest.User.UserName, tokenList[0]);
                        break;

                    //Sending a data message to guests in a specific room
                    case "DATAROOM":
                        {
                            roomName = tokenList[0];
                            string data = tokenList[1];

                            //locate the room
                            lock (RoomLock)
                            {
                                foreach(PrismRoom checkRoom in Rooms)
                                    if (checkRoom.RoomName == roomName)
                                    {
                                        checkRoom.SendToGuests("DATA", theGuest.User.UserName, data);
                                        break;
                                    }
                            }
                        }
                        break;

                    //Requesting data on a user                                          
                    case "REQUESTUSERINFO":
                        PrismGuest findGuest = FindGuest(tokenList[0]);
                        if (findGuest != null && findGuest.User != null)
                        {
                            //clone it
                            PrismUser cloned = new PrismUser();
                            string s = findGuest.User.ToString();
                            cloned.FromString(s);
                            if (cloned.UserName != theGuest.User.UserName)
                                cloned.Password = "";
                            theGuest.WriteTokens("USERINFOCHANGE", cloned.ToString());
                        }
                        break;

                    //Save the contents of the user to the database - also update the local instance
                    case "SAVEUSER":
                        string msg = "";

                        //Create a local copy of the user
                        userString = tokenList[0];
                        PrismUser user = new PrismUser();
                        user.FromString(userString);

                        //Check validity of password - user name should never be changed!
                        if (Storage.CheckPassword(user.Password, ref msg))
                        {
                            //Find the guest that has already loaded this user data (if any)
                            PrismGuest matchingGuest = FindGuest(user.UserName);

                            //If it exists, copy the data to this instance
                            if (matchingGuest != null)
                            {
                                userString = tokenList[0];
                                matchingGuest.User.FromString(userString, false);

                                //Echo the change of information to any users in the same room
                                if (matchingGuest.Room != null)
                                {
                                    userString = tokenList[0];
                                    PrismUser noPassword = new PrismUser();
                                    string newUser = userString;
                                    noPassword.FromString(newUser);                                  
                                    noPassword.Password = "";
                                    userString = noPassword.ToString();
                                    matchingGuest.Room.SendToGuests("USERINFOCHANGE", userString);
                                }
                            }

                            //Save change to Prism's database, whether they are logged in or not
                            lock (Storage)
                            {
                                Storage.StoreUserInfo(matchingGuest.User);
                            }                            
                        }
                        else
                            theGuest.WriteTokens("ERROR", "Could not change User Info: " + msg);
                        break;

                    //Server Stats were requested
                    case "SERVERSTATS":
                        PrismServerStats stats = GetServerStats();
                        theGuest.WriteTokens("SERVERSTATS", stats.ToString());
                        break;

                        //Room list was requested
                    case "ROOMLIST":
                        string rooms = "";
                        lock(RoomLock)
                        {
                            foreach (PrismRoom r in Rooms)
                                if (r.SubjectName == theGuest.SubjectName)
                                    rooms += r.RoomName + "^" + r.RoomGuests.Count + "^" + r.Locked + "^";
                        }
                        theGuest.WriteTokens("ROOMLIST", rooms);
                        break;

                    //Custom command received from client - allow either client app to
                    //process it, or an assigned CustomCommandProcessor object
                    case "CUSTOM":
                        if (CustomCommandReceived != null)
                            CustomCommandReceived(this, new ServerCustomCommandEventArgs(theGuest, tokenList[0], tokenList[1]));
                        foreach (CustomCommandProcessor cc in CommandModules)
                            cc.ProcessCustomCommand(this, theGuest, (string)tokenList[0], (string)tokenList[1]);                  
                        break;
                }
                Thread.Sleep(10);
            }                    
            //Remove the Guest from the list once Server closed
            RemoveGuest(theGuest);                            
        }

        //Return the "Lobby" room for a subject - create one if needed
        private PrismRoom GetLobby(string subjectName)
        {
            PrismRoom lobby = FindRoom(subjectName, LobbyName);
            if (lobby != null)
                return lobby;
            lobby = new PrismRoom(subjectName, LobbyName);
            lobby.Persistent = true;
            AddRoom(lobby);
            return lobby;
        }

        //Enforce the single user name sign in restriction
        private bool CheckSingleUserName(string userName, PrismGuest guest)
        {
            if (ProhibitSameUserName)
            {
                lock (GuestLock)
                {
                    foreach(PrismGuest g in Guests)
                        if (g.User != null)
                            if (g.User.UserName == userName)
                                if (g != guest)
                                {
                                    guest.WriteTokens("LOGINERROR", "Multiple Logins using same User Name probibited");
                                    return false;
                                }
                }                
            }
            return true;
        }

        //Return the Room List String that is returned to new guests
        private String RoomListString(string subjectName)
        {
            Tokenizer tok = new Tokenizer();
            lock (RoomLock)
            {
                foreach (PrismRoom room in Rooms)
                    if (room.SubjectName == subjectName)
                        tok.AppendToken(room.RoomName);
            }
            return tok.Result;
        }
    }

    //PrismServer Exception classes
    public class PrismServerException : ApplicationException
    {
        public PrismServerException(string message)
            : base(message)
        {
        }
    }

    //event args class for custom events
    public class ServerCustomCommandEventArgs : CustomCommandEventArgs
    {
        public ServerCustomCommandEventArgs(PrismGuest guest, string command, string param)
            : base(command, param)
        {
            _guest = guest;
        }

        public PrismGuest Guest
        {
            get
            {
                return _guest;
            }
        }

        private PrismGuest _guest;
    }

    //event to bubble exceptions
    public class ExceptionEventArgs : EventArgs
    {
        public ExceptionEventArgs(Exception ex, string source)
            : base()
        {
            Exception = ex;
            Source = source;
        }
        public Exception Exception { get; set; }
        public string Source { get; set; }
    }
}
